﻿using Sustainalytics.Claims;
using Sustainalytics.Entities;
using Sustainalytics.DataLayer;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;

namespace Sustainalytics.DynamicsCRMGateway
{
    
    public class ClaimsManager : IClaimsManager
    {
        private class InnerFeatureClaims
        {
            public Guid FeatureId;
            public string FeatureName;
            public Guid? ParentFeatureId;
            public Claim ClaimInfo;
        }
        private IDynamicsCrmGateway _crmGateway;
        private IAccountsRepository _accountRepository;
        private IEnumerable<InnerFeatureClaims> _featureWithClaims;
        private IEnumerable<Guid> _parentFeatures;
        private ICRMEntityDumper _crmEntityDumper;

        private static string _issuer = "Sustainalytics";
        
        public ClaimsManager(IDynamicsCrmGateway crmGateway, IAccountsRepository accountRepository, ICRMEntityDumper crmEntityDumper)
        {
            _crmGateway = crmGateway;
            _accountRepository = accountRepository;
            _crmEntityDumper = crmEntityDumper;
        }

        public IEnumerable<ClaimMap> ProcessFeatures()
        {
            Reset();
            LinkFeatureToClaims();

            return from ft in _featureWithClaims
                   group ft.ClaimInfo by ft.FeatureName into g
                   select new ClaimMap() { Id = Guid.NewGuid(), Permission = g.Key, Claims = g.Contains(null) ? new List<Claim>() : g.Distinct(new ClaimComparer()).ToList() };
        }
        public IEnumerable<UserClaim> ProcessAccounts()
        {
            LinkFeatureToClaims();
            return LinkAccountToFeatures();
        }

        private void Reset()
        {
            _featureWithClaims = null;
        }

        private void LinkFeatureToClaims()
        {
            if (_featureWithClaims == null)
            {
                var allFeatures = _crmGateway.ListFeatures();
                _crmEntityDumper.SerializeFeatures(allFeatures);
                var allClaims = _crmGateway.ListClaims();
                _crmEntityDumper.SerializeClaims(allClaims);
                var allFeatureClaims = _crmGateway.ListFeatureClaims();
                _crmEntityDumper.SerializeFeatureClaims(allFeatureClaims);

                _parentFeatures = from f_parent in allFeatures
                                  join f_child in allFeatures on f_parent.Id equals f_child.ParentFeatureRef == null ? null : (Guid?)f_child.ParentFeatureRef.Id
                                  select f_parent.Id;

                _parentFeatures = _parentFeatures.Distinct();

                _featureWithClaims = (from f in allFeatures
                                      join fc in allFeatureClaims on f.Id equals fc.FeatureId
                                      join c in allClaims on fc.ClaimId equals c.Id
                                      where !_parentFeatures.Contains(f.Id)
                                      select new InnerFeatureClaims()
                                      {
                                          FeatureId = f.Id,
                                          ParentFeatureId = f.ParentFeatureRef == null ? null : (Guid?)f.ParentFeatureRef.Id,
                                          FeatureName = f.Name,
                                          ClaimInfo = new Claim(c.Name, c.Value, ClaimValueTypes.String, _issuer)
                                      }).ToList();
            }
        }

        private IEnumerable<UserClaim> LinkAccountToFeatures()
        {
            var accounts = _accountRepository.GetAccountsIncludingNameOnly(Status.Active, true);
            _crmEntityDumper.SerializeAccounts(accounts);
            var allAcountFeatures = _crmGateway.ListAccountFeatures().Where(af => af.AccountRef != null && af.FeatureRef != null);//remove garbadge
            _crmEntityDumper.SerializeAccountFeatures(allAcountFeatures);

            var directLinkedAccountClaimQuery = from a in accounts
                                                join af in allAcountFeatures on a.Id equals af.AccountRef.Id
                                                join f in _featureWithClaims on af.FeatureRef.Id equals f.FeatureId
                                                select new
                                                {
                                                    AccountName = a.Name,
                                                    AccountId = a.Id,
                                                    FeatureId = f.FeatureId,
                                                    ClaimInfo = f.ClaimInfo
                                                };

            var packageLinkedAccountClaimQuery = from a in accounts
                                                 join af in allAcountFeatures on a.Id equals af.AccountRef.Id
                                                 join f in _featureWithClaims on af.FeatureRef.Id equals f.ParentFeatureId
                                                 select new
                                                 {
                                                     AccountName = a.Name,
                                                     AccountId = a.Id,
                                                     FeatureId = f.FeatureId,
                                                     ClaimInfo = f.ClaimInfo
                                                 };
            var distinctAccountsWithFeatures = allAcountFeatures.Select(af=>af.AccountRef.Id).Distinct();

            var noClaimsAccounts = from a in accounts
                                   where !distinctAccountsWithFeatures.Contains(a.Id)
                                   select new UserClaim()
                                    {
                                        Id = a.Id,
                                        Account = a.Name,
                                        Claims = new List<Claim>()
                                    };

            return (from ac in directLinkedAccountClaimQuery.ToList().Union(packageLinkedAccountClaimQuery.ToList())
                   group ac.ClaimInfo by new { ac.AccountId, ac.AccountName } into g
                   select new UserClaim() { Id = g.Key.AccountId, Account = g.Key.AccountName, Claims = g.Distinct(new ClaimComparer()).ToList() }).Union(noClaimsAccounts.ToList());
        }
    }
}
